#include "SocketFunctions.h"

/*---------------------------------------------------------------------------------
--
--	FUNCTION: Create_Socket
--
--	DATE: Feb. 20, 2010
--
--	REVISIONS (Date and Description):
--
--	DESIGNER: Renz Barron
--
--	PROGRAMMER: Renz Barron
--
--	INTERFACE: SOCKET SocketFunctions::Create_Socket(BOOL protocol)
--
--      BOOL protocol: The protocol (TCP or UDP) to be used for the socket.
--
--	RETURNS: SOCKET (the one created by this function)
--
--	NOTES: Wrapper function used to create a socket which can be of type TCP or
--         UDP.
--
/*-------------------------------------------------------------------------------*/
SOCKET SocketFunctions::Create_Socket(BOOL protocol)
{
	char    str[SIZEERROR];
	
	SOCKET  new_socket;

	if(protocol == TCP)
	{
		new_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 
	}
	
	// Else, UDP
	else
	{
		new_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	}

	// Error creating socket
	if(new_socket == INVALID_SOCKET)
	{
		sprintf_s(str, SIZEERROR, "Could not create socket: %d", WSAGetLastError());

		Connections::WSAFatal(str, "Error: socket creation");
	}
	
	return new_socket;
}

/*------------------------------------------------------------------------------------------
--
--	FUNCTION: SetSocketOptions
--
--	DATE: March. 17, 2010
--
--	REVISIONS (Date and Description):
--
--	DESIGNER: Renz Barron
--
--	PROGRAMMER: Renz Barron
--
--	INTERFACE: bool SocketFunctions::SetSocketOptions(int broadcastType, int type, 
--                                                    SOCKET connection)
--
--      int broadcastType: Type of streaming; MULTICAST, DOWNLOAD, UPLOAD, STREAMING, 
--                         etc.
-- 
--      int type: Server or Client
--
--      SOCKET connection: The socket to set the options on.
--
--	RETURNS: void
--
--	NOTES: Configures the connection to join the multicast group.
--
/*----------------------------------------------------------------------------------------*/
bool SocketFunctions::SetSocketOptions(int broadcastType, int type, SOCKET connection)
{
	char     str[SIZEERROR],
	         achMCAddr[16] = TIMECAST_ADDR;

	int      ret,
	         sndsize = 65536;
	
	struct   ip_mreq stMreq;
	
	u_long   lTTL = 2;
	
	BOOL     fFlag,
			 tmp;
	
	if(broadcastType == MULTICAST) 
	{
		if(type == SERVER) 
		{
			stMreq.imr_multiaddr.s_addr = inet_addr(achMCAddr);
			stMreq.imr_interface.s_addr = INADDR_ANY;
			
			// Add an IP group membership
			ret = setsockopt(connection, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&stMreq, sizeof(stMreq));
			
			if(ret == SOCKET_ERROR) 
			{
				fprintf(stderr, "Error setting socket operation: %d", WSAGetLastError());
				
				return false;
			}

			// Set IP TTL to traverse up to multiple routers
			ret = setsockopt(connection, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&lTTL, sizeof(lTTL));
			
			if(ret == SOCKET_ERROR) 
			{
				fprintf(stderr, "Error setting socket operation: %d", WSAGetLastError());
				
				return false;
			}

			fFlag = FALSE;
			
			// Disable loopback
			ret = setsockopt(connection, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&fFlag, sizeof(fFlag));
			
			if(ret == SOCKET_ERROR) 
			{
				fprintf(stderr, "Error setting socket operation: %d", WSAGetLastError());

				return false;
			}
		} 
		
		// Client socket will be bound to the same address already in use
		else 
		{
			fFlag = true;
			
			ret = setsockopt(connection, SOL_SOCKET, SO_REUSEADDR, (char *)&fFlag, sizeof(fFlag));
			
			if(ret == SOCKET_ERROR) 
			{
				fprintf(stderr, "Error setting socket operation: %d", WSAGetLastError());
				
				return false;
			}
		}
	} 
	
	// No Multicast; set the send and receive buffers for the socket
	else 
	{
		ret = setsockopt(connection, SOL_SOCKET, SO_RCVBUF, (char *)&sndsize,(int)sizeof(sndsize));
		ret = setsockopt(connection, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize,(int)sizeof(sndsize));

		// Bind to the same address already in use
		if(setsockopt(connection, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp, sizeof(tmp)) == SOCKET_ERROR)
		{
			sprintf_s(str, SIZEERROR, "Could not change the socket options: %d", WSAGetLastError());
			
			Connections::WSAFatal(str, "ERROR: setsockopt");
			
			return false;
		}
	}
  
	return true;
}

/*---------------------------------------------------------------------------------------------
--
--	FUNCTION: SocketBind
--
--	DATE: March. 17, 2010
--
--	REVISIONS (Date and Description):
--
--	DESIGNER: Renz Barron
--
--	PROGRAMMER: Renz Barron
--
--	INTERFACE: bool SocketFunctions::SocketBind(SOCKET socket, int port, int broadcastType, 
--                                              bool type)
--
--      SOCKET socket: The socket the address is being bound on.
--
--      int port: The port of the server.
--
--      int broadcastType: Type of streaming; MULTICAST, DOWNLOAD, UPLOAD, STREAMING, 
--                         etc.
--
--      bool type: Server or client.
--
--	RETURNS: void
--
--	NOTES: Wrapper function to bind the socket to a specified port using a selected
--         broadcast type. If the broadcast type is MULTICAST and the one requesting
--         a connection to the socket is a client, its address will be added as part
--         of the destination when data is sent.
--
/*-------------------------------------------------------------------------------------------*/
bool SocketFunctions::SocketBind(SOCKET socket, int port, int broadcastType, bool type)
{
	struct   sockaddr_in socket_info;
	struct   ip_mreq stMreq;

	char     str[SIZEERROR],
	         achMCAddr[MAXADDRSTR] = TIMECAST_ADDR;

	int      ret;

	// Initialize and set up the address data structure
	memset((char *)&socket_info, 0, sizeof(struct sockaddr_in)); 
  
	socket_info.sin_family = AF_INET;
	socket_info.sin_port = htons(port); 
  
	// Accept connections from any client
	socket_info.sin_addr.s_addr = htonl(INADDR_ANY);
	
	// Bind an address to the socket
	if(bind(socket, (struct sockaddr *)&socket_info, sizeof(socket_info)) == SOCKET_ERROR)
	{
		sprintf_s(str, SIZEERROR, "Can't bind name to socket: %d", WSAGetLastError());
		
		Connections::WSAFatal(str, "ERROR: bind");
		
		return false;
	}

	// Assign our destination address; join multicast group so we can receive from it
	if(broadcastType == MULTICAST && type == CLIENT) 
	{
		stMreq.imr_multiaddr.s_addr = inet_addr(achMCAddr);
		stMreq.imr_interface.s_addr = INADDR_ANY;

		ret = setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&stMreq, sizeof(stMreq));
		
		if(ret == SOCKET_ERROR) 
		{
			fprintf(stderr, "Error setting socket operation: %d", WSAGetLastError());
		
			return false;
		}
	}
  
	return true;
}
